﻿/*
	wp8libflac project
	© Alovchin, 2014
*/

#include "pch.h"

#include <string>
#include <sstream>
#include <iomanip>
#include <regex>
#include <sys\stat.h>

#include "BufferExtensions.h"
#include "FlacDecoder.h"
#include "FlacHelpers.h"

using namespace wp8::Interop;
using namespace Platform::Collections;
using namespace Windows::Foundation::Collections;

void FlacDecoder::Initialize(String^ fileName)
{
	errno_t err = _wfopen_s(&m_pFile, fileName->Data(), L"rb");
	if (err != 0)
	{
		throw ref new InvalidArgumentException("Failed to open specified file.");
	}

	auto decoderInitStatus = FLAC__stream_decoder_init_stream(
		this->m_flacDecoder,
		FlacDecoder::read_callback,
		FlacDecoder::seek_callback,
		FlacDecoder::tell_callback,
		FlacDecoder::length_callback,
		FlacDecoder::eof_callback,
		FlacDecoder::write_callback,
		FlacDecoder::metadata_callback,
		FlacDecoder::error_callback,
		(void*)this);

	if (FLAC__STREAM_DECODER_INIT_STATUS_OK != decoderInitStatus)
	{
		(void)FLAC__stream_decoder_finish(this->m_flacDecoder);
		throw ref new Exception(COR_E_INVALIDOPERATION, "Failed to initialize decoder.");
	}
}

FlacStreamInfo^ FlacDecoder::GetStreamInfo()
{
	this->EnsureMetadataRead();
	return this->m_streamInfo;
}

void FlacDecoder::EnsureMetadataRead()
{
	if (this->m_metadataRead)
	{
		return;
	}

	FLAC__bool result = FLAC__stream_decoder_process_until_end_of_metadata(this->m_flacDecoder);
	FLAC__StreamDecoderState state = FLAC__stream_decoder_get_state(this->m_flacDecoder);
	if (FLAC__STREAM_DECODER_END_OF_STREAM == state)
	{
		throw ref new Exception(COR_E_ENDOFSTREAM, "No metadata found, or unexpected call.");
	}

	this->m_metadataRead = true;
}

FlacSample^ FlacDecoder::GetSample()
{
	this->m_currentSample = nullptr;
	FLAC__bool result = FLAC__stream_decoder_process_single(this->m_flacDecoder);
	return this->m_currentSample;
}

void FlacDecoder::Seek(int64 position)
{
	if (position < 0)
	{
		throw ref new InvalidArgumentException("Absolute position cannot be less than 0.");
	}

	if (this->Position == position)
	{
		return;
	}

	this->EnsureMetadataRead();
	if (0 == this->m_streamInfo->BitsPerSample)
	{
		throw ref new Exception(COR_E_INVALIDOPERATION, "Cannot seek current stream.");
	}

	FLAC__bool result = FLAC__stream_decoder_seek_absolute(
		this->m_flacDecoder, (uint64)(position / this->m_streamInfo->BitsPerSample));
	if (0 == result)
	{
		throw ref new Exception(COR_E_ARGUMENTOUTOFRANGE, "Position overflow.");
	}
}

void FlacDecoder::Close()
{
	(void)FLAC__stream_decoder_finish(this->m_flacDecoder);

	if (this->m_pFile != nullptr)
	{
		fclose(this->m_pFile);
		this->m_pFile = nullptr;
	}
}


#define GET_INSTANCE(x) { reinterpret_cast<FlacDecoder^>(x) }

FLAC__StreamDecoderReadStatus FlacDecoder::read_callback(
	const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, void* client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	if (*bytes > 0)
	{
		*bytes = fread_s(buffer, *bytes, sizeof(FLAC__byte), *bytes, instance->m_pFile);

		if (ferror(instance->m_pFile) != 0)
		{
			return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
		}

		if (*bytes == 0)
		{
			return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
		}

		return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
	}

	return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}

FLAC__StreamDecoderWriteStatus FlacDecoder::write_callback(
	const FLAC__StreamDecoder* decoder,
	const FLAC__Frame* frame,
	const FLAC__int32* const buffer[],
	void* client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	int bytesCount = frame->header.blocksize * ((frame->header.channels * frame->header.bits_per_sample) >> 3);
	IBuffer^ bytesBuffer = ref new Buffer(bytesCount);

	int bufferSize = frame->header.blocksize * frame->header.channels;
	auto bytesRead = PackSample(buffer, frame->header.blocksize, frame->header.channels, bytesBuffer, frame->header.bits_per_sample);

	int64 avgBytesPerSec = frame->header.sample_rate * frame->header.channels * (frame->header.bits_per_sample / 8);
	double position = (double)bytesRead / avgBytesPerSec;

	bytesBuffer->Length = bytesRead;
	instance->m_currentSample = ref new FlacSample(bytesBuffer, position);

	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

FLAC__StreamDecoderSeekStatus FlacDecoder::seek_callback(
	const FLAC__StreamDecoder* decoder, FLAC__uint64 absolute_byte_offset, void* client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	if (_fseeki64(instance->m_pFile, (__int64)absolute_byte_offset, SEEK_SET) < 0)
	{
		return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
	}

	return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}

FLAC__StreamDecoderTellStatus FlacDecoder::tell_callback(
	const FLAC__StreamDecoder* decoder, FLAC__uint64* absolute_byte_offset, void* client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	__int64 pos = _ftelli64(instance->m_pFile);

	if (pos < 0)
	{
		return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
	}

	*absolute_byte_offset = (FLAC__uint64)pos;
	return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}

FLAC__StreamDecoderLengthStatus FlacDecoder::length_callback(
	const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	struct __stat64 filestats;
	int err = _fstat64(_fileno(instance->m_pFile), &filestats);

	if (err != 0)
	{
		return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
	}

	*stream_length = (FLAC__uint64)filestats.st_size;
	return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}

void FlacDecoder::metadata_callback(
	const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	if (FLAC__METADATA_TYPE_STREAMINFO == metadata->type)
	{
		auto blockAlign = metadata->data.stream_info.channels * (metadata->data.stream_info.bits_per_sample / 8);
		auto avgBytesPerSec = metadata->data.stream_info.sample_rate * blockAlign;

		int64 streamLength = 0;
		double duration = (double)metadata->data.stream_info.total_samples / metadata->data.stream_info.sample_rate;

		auto streamInfo = ref new FlacStreamInfo(duration, avgBytesPerSec, streamLength);
		streamInfo->BitsPerSample = metadata->data.stream_info.bits_per_sample;

#if USE_WAVEHEADER
		streamInfo->WaveHeader = CreateWaveHeader(metadata->data.stream_info, &streamLength);
#endif

#if USE_CODECDATA
		WAVEFORMATEX codecFmt =
		{
			(WORD)WAVE_FORMAT_PCM,
			(WORD)metadata->data.stream_info.channels,
			(DWORD)metadata->data.stream_info.sample_rate,
			(DWORD)avgBytesPerSec,
			(WORD)blockAlign,
			(WORD)metadata->data.stream_info.bits_per_sample,
			0
		};

		std::wostringstream ssHex;
		ssHex << std::hex << std::setfill(L'0');		
		ssHex << std::setw(4) << _byteswap_ushort(codecFmt.wFormatTag);
		ssHex << std::setw(4) << _byteswap_ushort(codecFmt.nChannels);
		ssHex << std::setw(8) << _byteswap_ulong(codecFmt.nSamplesPerSec);
		ssHex << std::setw(8) << _byteswap_ulong(codecFmt.nAvgBytesPerSec);
		ssHex << std::setw(4) << _byteswap_ushort(codecFmt.nBlockAlign);
		ssHex << std::setw(4) << _byteswap_ushort(codecFmt.wBitsPerSample);
		ssHex << std::setw(4) << _byteswap_ushort(codecFmt.cbSize);

		streamInfo->CodecData = ref new String(ssHex.str().c_str());
		ssHex.clear();
#else
		streamInfo->SampleRate = metadata->data.stream_info.sample_rate;
		streamInfo->ChannelCount = metadata->data.stream_info.channels;
#endif

		instance->m_streamInfo = streamInfo;
	}
}

FLAC__bool FlacDecoder::eof_callback(const FLAC__StreamDecoder* decoder, void* client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	return feof(instance->m_pFile) ? 1 : 0;
}

void FlacDecoder::error_callback(
	const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data)
{
	FlacDecoder^ instance = GET_INSTANCE(client_data);

	String^ message;
	switch (status)
	{
	case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
		message = "The decoder encountered a corrupted frame header.";
		break;
	case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
		message = "The frame's data did not match the CRC in the footer.";
		break;
	case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
		message = "An error in the stream caused the decoder to lose synchronization.";
		break;
	case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM:
		message = "The decoder encountered reserved fields in use in the stream.";
		break;
	default:
		message = "Unknown error occured in the decoder.";
		break;
	}

	instance->ErrorCallback(message);
}

#undef GET_INSTANCE
